Skip to content

feat(js,shared): add Prosopo Procaptcha as a CAPTCHA provider - #8944

Open
forgetso wants to merge 8 commits into
clerk:mainfrom
forgetso:feat/prosopo-captcha-provider
Open

feat(js,shared): add Prosopo Procaptcha as a CAPTCHA provider#8944
forgetso wants to merge 8 commits into
clerk:mainfrom
forgetso:feat/prosopo-captcha-provider

Conversation

@forgetso

@forgetso forgetso commented Jun 22, 2026

Copy link
Copy Markdown

Hey @tmilewski @alexcarpenter. This PR adds Prosopo as an opt-in alternative to Turnstile, behind displayConfig.captchaProvider.

Why

Prosopo is has EU data residency and advanced bot detection - harder to bypass than default Cloudflare Turnstile.

What

  • CaptchaProvider widened to 'turnstile' | 'prosopo' (default unchanged).
  • New getProcaptchaToken; getCaptchaToken dispatches by provider.
  • Modal/smart/invisible decision pulled into a shared resolveCaptchaContainer helper used by both providers.
  • CaptchaChallenge.invisible() now reads captchaProvider from displayConfig instead of hardcoding 'turnstile'.

Tests

16 unit tests, mocked window.procaptcha (same pattern as the existing Turnstile suite — neither hits a live CDN).

Backend dependency

Needs display_config.captcha_provider: "prosopo" from FAPI to actually fire. Frontend half lands first; happy to coordinate on the backend side.

Please let me know your thoughts.

Adds 'prosopo' alongside 'turnstile' in CaptchaProvider and a new
getProcaptchaToken that mirrors the invisible + smart flows from
turnstile.ts. When displayConfig.captchaProvider is 'prosopo', clerk-js
loads the Procaptcha bundle from js.prosopo.io and renders into the
existing invisible/smart containers. Turnstile remains the default.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 22, 2026

Copy link
Copy Markdown

@forgetso is attempting to deploy a commit to the Clerk Production Team on Vercel.

A member of the Team first needs to authorize it.

@changeset-bot

changeset-bot Bot commented Jun 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 721452b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 23 packages
Name Type
@clerk/clerk-js Minor
@clerk/shared Minor
@clerk/chrome-extension Patch
@clerk/electron Patch
@clerk/expo Patch
@clerk/astro Patch
@clerk/backend Patch
@clerk/expo-passkeys Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/headless Patch
@clerk/hono Patch
@clerk/localizations Patch
@clerk/msw Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/react Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch
@clerk/ui Patch
@clerk/vue Patch
@clerk/swingset Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: fd8e6e4f-7647-4b55-b5f7-f5a8241fb761

📥 Commits

Reviewing files that changed from the base of the PR and between 3276a3f and 721452b.

📒 Files selected for processing (12)
  • .changeset/prosopo-captcha-provider.md
  • packages/clerk-js/src/utils/captcha/CaptchaChallenge.ts
  • packages/clerk-js/src/utils/captcha/__tests__/CaptchaChallenge.test.ts
  • packages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.ts
  • packages/clerk-js/src/utils/captcha/__tests__/getCaptchaToken.test.ts
  • packages/clerk-js/src/utils/captcha/__tests__/prosopo.test.ts
  • packages/clerk-js/src/utils/captcha/__tests__/turnstile.test.ts
  • packages/clerk-js/src/utils/captcha/containerResolver.ts
  • packages/clerk-js/src/utils/captcha/getCaptchaToken.ts
  • packages/clerk-js/src/utils/captcha/prosopo.ts
  • packages/clerk-js/src/utils/captcha/turnstile.ts
  • packages/shared/src/types/displayConfig.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
🚧 Files skipped from review as they are similar to previous changes (9)
  • packages/clerk-js/src/utils/captcha/tests/CaptchaChallenge.test.ts
  • packages/clerk-js/src/utils/captcha/CaptchaChallenge.ts
  • packages/clerk-js/src/utils/captcha/tests/getCaptchaToken.test.ts
  • packages/shared/src/types/displayConfig.ts
  • packages/clerk-js/src/utils/captcha/getCaptchaToken.ts
  • packages/clerk-js/src/utils/captcha/prosopo.ts
  • .changeset/prosopo-captcha-provider.md
  • packages/clerk-js/src/utils/captcha/turnstile.ts
  • packages/clerk-js/src/utils/captcha/containerResolver.ts

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

Adds Prosopo Procaptcha as an alternative CAPTCHA provider. A shared container resolver handles modal, smart, and invisible containers for both providers. Token generation routes by captchaProvider. Turnstile uses the shared resolver and cleanup logic. The public CaptchaProvider type now accepts 'turnstile' | 'prosopo'. Tests cover provider routing, container lifecycle, and Prosopo flows.

Estimated code review effort: 4 (Complex) | ~60 minutes

Merge Risk: ⚪ Minimal · up to 72145

The change is merge-ready after normal checks, with one localized test-isolation cleanup to restore real timers after each test; no actionable merge-blocking risk remains.

Changes

Area Summary
Provider contract Adds 'prosopo' to CaptchaProvider and documents the package release changes.
Container handling Adds shared container resolution and cleanup for modal, smart, and invisible CAPTCHA flows.
Prosopo integration Adds script loading, widget rendering, callbacks, token extraction, reset, and cleanup.
Turnstile integration Refactors Turnstile to use the shared container utilities.
Tests Adds coverage for provider dispatch, container lifecycle, Prosopo flows, and Turnstile rendering.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 11 files. (1 skipped: 1… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the primary change: adding Prosopo Procaptcha as a CAPTCHA provider for JavaScript and shared packages.
Description check ✅ Passed The description directly explains the Prosopo integration, provider selection, shared container resolution, backend dependency, and tests. It is related to the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 11 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

forgetso and others added 2 commits June 22, 2026 16:58
Pull the modal/smart/invisible container decision out of turnstile.ts and
prosopo.ts into a single resolveCaptchaContainer helper, plus a matching
cleanupCaptchaContainer for the side effects (modal close, invisible div
removal). Each provider keeps its own visual styling (Turnstile's
maxHeight/min-height dance, Prosopo's reserved height) and its own
render() call.

Also:
- Point the Procaptcha load-failure warning at js.prosopo.io + Prosopo
  CSP docs rather than the Cloudflare-flavored guidance.
- Add router test for getCaptchaToken (turnstile vs prosopo dispatch).
- Add CaptchaChallenge test that .invisible() and .managedOrInvisible()
  both honour displayConfig.captchaProvider.
- Expand prosopo.test.ts with smart, modal, modal-not-ready, error-
  callback, expired-callback, and script-load-failure cases. Validates
  the warning now points at Prosopo's CSP docs.

15 captcha tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Procaptcha runtime calls error-callback with an Error built from the
underlying challenge failure (procaptcha/modules/Manager.ts:193 and
procaptcha-frictionless/ProcaptchaFrictionless.tsx:222,240 both call
events.onError(new Error(message)), which defaultCallbacks.ts:129
forwards to the user-supplied error-callback).

Earlier draft of this file assumed no argument was passed and rejected
with a hardcoded 'procaptcha_error'. Type the callback the way the
runtime actually behaves and surface error.message, with the previous
identifier kept as a defensive fallback. New test asserts both branches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@forgetso
forgetso marked this pull request as ready for review June 23, 2026 09:40

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/clerk-js/src/utils/captcha/containerResolver.ts`:
- Around line 91-97: The invisible CAPTCHA container resolver creates containers
with a shared class name selector that causes interference when multiple CAPTCHA
instances run concurrently. Instead of using the shared
CAPTCHA_INVISIBLE_CLASSNAME class selector, generate a unique identifier per
instance (such as a data attribute with a generated ID) for the created div
element, then return that unique selector in the containerSelector field. Update
the callers in turnstile.ts and prosopo.ts to pass the resolved unique container
selector into their cleanup functions instead of relying on the shared class
query.
- Around line 63-64: The waitForElement call for modalContainerQuerySelector has
no timeout and can hang indefinitely if the modal container never appears. Wrap
the waitForElement promise with a timeout mechanism that rejects after a
reasonable duration if the element is not found, and throw a captchaError with
an appropriate error message when the timeout is exceeded. This ensures the auth
flow fails fast rather than hanging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: f69936f1-602b-4123-a553-15e3b7761394

📥 Commits

Reviewing files that changed from the base of the PR and between 7e3174a and b784d1e.

📒 Files selected for processing (10)
  • .changeset/prosopo-captcha-provider.md
  • packages/clerk-js/src/utils/captcha/CaptchaChallenge.ts
  • packages/clerk-js/src/utils/captcha/__tests__/CaptchaChallenge.test.ts
  • packages/clerk-js/src/utils/captcha/__tests__/getCaptchaToken.test.ts
  • packages/clerk-js/src/utils/captcha/__tests__/prosopo.test.ts
  • packages/clerk-js/src/utils/captcha/containerResolver.ts
  • packages/clerk-js/src/utils/captcha/getCaptchaToken.ts
  • packages/clerk-js/src/utils/captcha/prosopo.ts
  • packages/clerk-js/src/utils/captcha/turnstile.ts
  • packages/shared/src/types/displayConfig.ts

Comment thread packages/clerk-js/src/utils/captcha/containerResolver.ts Outdated
Comment thread packages/clerk-js/src/utils/captcha/containerResolver.ts
forgetso and others added 2 commits June 23, 2026 11:50
- waitForElement on the modal container is unbounded, so a missing modal
  container could hang the auth flow indefinitely. Race against a 5s
  timeout and throw { captchaError: 'modal_container_not_found' } on
  expiry.
- The invisible flow used a shared .clerk-invisible-captcha class
  selector for both render and cleanup. Two concurrent challenges would
  render into the same node and the second cleanup could remove the
  first's container. Mint a per-instance id ('<class>-<ts>-<n>'),
  return that id as the selector, and pass it into cleanup so each
  challenge only removes its own node.
- New containerResolver.test.ts covers the timeout path and asserts
  concurrent invisible challenges resolve to distinct containers and
  clean up independently. Existing turnstile.test.ts updated to match
  the new id-based selector format (the element still carries the
  class).

Both issues were pre-existing in turnstile.ts and inherited by the
refactor; flagged by CodeRabbit on PR clerk#8944.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/clerk-js/src/utils/captcha/containerResolver.ts`:
- Around line 129-133: The exported function cleanupCaptchaContainer is missing
an explicit return type annotation. Add `: void` to the function signature after
the closing parenthesis of the parameter list to comply with the coding
guideline requiring explicit return types for exported functions.
- Around line 71-79: The MutationObserver created inside the waitForElement
function is not being cleaned up when the Promise.race timeout branch resolves
first, causing the observer to remain connected to document.body indefinitely.
Fix this by either modifying the waitForElement function to accept an abort
signal that it uses to clean up the observer when signaled, then create an
AbortController in the calling code and trigger it after the Promise.race
resolves with a timeout result, or alternatively add explicit cleanup logic in
the calling code after the race resolves to properly stop and disconnect the
observer that was created inside waitForElement. The key is ensuring the
MutationObserver is disconnected regardless of whether the race completes due to
element found or timeout.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 42e2fd3e-0ea0-4c52-9117-79f7964a9322

📥 Commits

Reviewing files that changed from the base of the PR and between b784d1e and 3629098.

📒 Files selected for processing (5)
  • packages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.ts
  • packages/clerk-js/src/utils/captcha/__tests__/turnstile.test.ts
  • packages/clerk-js/src/utils/captcha/containerResolver.ts
  • packages/clerk-js/src/utils/captcha/prosopo.ts
  • packages/clerk-js/src/utils/captcha/turnstile.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/clerk-js/src/utils/captcha/turnstile.ts
  • packages/clerk-js/src/utils/captcha/prosopo.ts

Comment on lines +71 to +79
// waitForElement never rejects, so race it against a timeout to keep the auth flow from hanging.
const el = await Promise.race<Element | null>([
waitForElement(modalContainerQuerySelector),
new Promise<null>(resolve => setTimeout(() => resolve(null), MODAL_CONTAINER_TIMEOUT_MS)),
]);
if (!el) {
// eslint-disable-next-line @typescript-eslint/only-throw-error
throw { captchaError: 'modal_container_not_found' };
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect waitForElement implementation for observer cleanup / abort support
rg -nP --type=ts -C8 'waitForElement' packages/shared/src

Repository: clerk/javascript

Length of output: 1219


🏁 Script executed:

cat -n packages/shared/src/dom/waitForElement.ts

Repository: clerk/javascript

Length of output: 896


🏁 Script executed:

cat -n packages/clerk-js/src/utils/captcha/containerResolver.ts | sed -n '65,85p'

Repository: clerk/javascript

Length of output: 1325


Fix waitForElement observer cleanup on timeout.

The waitForElement promise remains unsettled when the timeout branch wins in Promise.race, leaving the MutationObserver connected to document.body indefinitely. Each failed modal mount leaks an observer that continues to fire on DOM mutations for the rest of the page session.

Either add abort signal/timeout support to waitForElement so it cleans up on timeout, or explicitly clean up the observer in the calling code after the race resolves.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/clerk-js/src/utils/captcha/containerResolver.ts` around lines 71 -
79, The MutationObserver created inside the waitForElement function is not being
cleaned up when the Promise.race timeout branch resolves first, causing the
observer to remain connected to document.body indefinitely. Fix this by either
modifying the waitForElement function to accept an abort signal that it uses to
clean up the observer when signaled, then create an AbortController in the
calling code and trigger it after the Promise.race resolves with a timeout
result, or alternatively add explicit cleanup logic in the calling code after
the race resolves to properly stop and disconnect the observer that was created
inside waitForElement. The key is ensuring the MutationObserver is disconnected
regardless of whether the race completes due to element found or timeout.

Comment on lines +129 to +133
export const cleanupCaptchaContainer = (
containerType: CaptchaContainerType,
opts: Pick<CaptchaOptions, 'closeModal'>,
containerSelector?: string,
) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add an explicit : void return type.

cleanupCaptchaContainer is an exported function but omits its return type.

♻️ Proposed change
 export const cleanupCaptchaContainer = (
   containerType: CaptchaContainerType,
   opts: Pick<CaptchaOptions, 'closeModal'>,
   containerSelector?: string,
-) => {
+): void => {

As per coding guidelines: "Always define explicit return types for functions, especially public APIs", and based on learnings to enforce explicit return type annotations for exported functions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/clerk-js/src/utils/captcha/containerResolver.ts` around lines 129 -
133, The exported function cleanupCaptchaContainer is missing an explicit return
type annotation. Add `: void` to the function signature after the closing
parenthesis of the parameter list to comply with the coding guideline requiring
explicit return types for exported functions.

Sources: Coding guidelines, Learnings

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@forgetso

forgetso commented Aug 7, 2026

Copy link
Copy Markdown
Author

Any thoughts here @nikosdouvlis?

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.ts (1)

51-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use typed mock references in both test assertions.

Both changed lines use any to access or configure mocks. This can hide drift in the mocked APIs.

  • packages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.ts#L51-L51: Use vi.mocked(waitForElement) with a type derived from waitForElement.
  • packages/clerk-js/src/utils/captcha/__tests__/turnstile.test.ts#L56-L56: Capture the typed render mock or add a narrow Window augmentation instead of using (window as any).

As per coding guidelines, avoid any in TypeScript and prefer narrowed types.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.ts` at
line 51, Replace the any-based mock access in
packages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.ts:51-51
with vi.mocked(waitForElement), using its inferred or explicitly derived type.
In packages/clerk-js/src/utils/captcha/__tests__/turnstile.test.ts:56-56,
capture a typed render mock or add a narrow Window augmentation, removing the
window as any cast while preserving the existing test behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.ts`:
- Around line 20-23: Update the existing afterEach cleanup in containerResolver
tests to call vi.useRealTimers() alongside vi.restoreAllMocks(), ensuring fake
timers are restored even when an awaited rejection exits early. Remove the later
success-path-only timer restoration if it becomes redundant.

In `@packages/clerk-js/src/utils/captcha/containerResolver.ts`:
- Around line 101-103: Update the fallback message in the Smart CAPTCHA
initialization path near the relevant console.error call to describe the Prosopo
configuration, including captcha_provider: "prosopo" and its configuration path,
before linking to the custom-flow CAPTCHA documentation.

---

Nitpick comments:
In `@packages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.ts`:
- Line 51: Replace the any-based mock access in
packages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.ts:51-51
with vi.mocked(waitForElement), using its inferred or explicitly derived type.
In packages/clerk-js/src/utils/captcha/__tests__/turnstile.test.ts:56-56,
capture a typed render mock or add a narrow Window augmentation, removing the
window as any cast while preserving the existing test behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: e64960b0-f94b-4e9e-bd37-5b933698e9fb

📥 Commits

Reviewing files that changed from the base of the PR and between 6100719 and 0e03a3f.

📒 Files selected for processing (12)
  • .changeset/prosopo-captcha-provider.md
  • packages/clerk-js/src/utils/captcha/CaptchaChallenge.ts
  • packages/clerk-js/src/utils/captcha/__tests__/CaptchaChallenge.test.ts
  • packages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.ts
  • packages/clerk-js/src/utils/captcha/__tests__/getCaptchaToken.test.ts
  • packages/clerk-js/src/utils/captcha/__tests__/prosopo.test.ts
  • packages/clerk-js/src/utils/captcha/__tests__/turnstile.test.ts
  • packages/clerk-js/src/utils/captcha/containerResolver.ts
  • packages/clerk-js/src/utils/captcha/getCaptchaToken.ts
  • packages/clerk-js/src/utils/captcha/prosopo.ts
  • packages/clerk-js/src/utils/captcha/turnstile.ts
  • packages/shared/src/types/displayConfig.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/shared/src/types/displayConfig.ts
  • packages/clerk-js/src/utils/captcha/tests/CaptchaChallenge.test.ts
  • packages/clerk-js/src/utils/captcha/tests/getCaptchaToken.test.ts
  • packages/clerk-js/src/utils/captcha/getCaptchaToken.ts
  • .changeset/prosopo-captcha-provider.md
  • packages/clerk-js/src/utils/captcha/CaptchaChallenge.ts
  • packages/clerk-js/src/utils/captcha/turnstile.ts
  • packages/clerk-js/src/utils/captcha/prosopo.ts

Comment on lines +20 to +23
afterEach(() => {
document.body.innerHTML = '';
vi.restoreAllMocks();
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate file =="
fd -a 'containerResolver.test.ts' . || true

echo "== File excerpt =="
file="$(fd 'containerResolver.test.ts' . | head -n 1)"
if [ -n "${file:-}" ]; then
  wc -l "$file"
  cat -n "$file"
fi

echo "== Relevant timer usages =="
file="$(fd 'containerResolver.test.ts' . | head -n 1 || true)"
if [ -n "${file:-}" ]; then
  rg -n "vi\.use(Fake|Real)Timers|waitForElement|await" "$file"
fi

echo "== Nearby tests summary =="
python3 - <<'PY'
from pathlib import Path
p = next(Path('.').rglob('containerResolver.test.ts'), None)
if not p:
    raise SystemExit
print(p)
text = p.read_text()
for i, line in enumerate(text.splitlines(), 1):
    if 'it(' in line or "vi.useFakeTimers" in line or "vi.useRealTimers" in line or 'afterEach' in line or 'waitForElement' in line:
        print(f"{i}: {line}")
PY

Repository: clerk/javascript

Length of output: 5024


🌐 Web query:

Vitest vi.useFakeTimers beforeEach afterEach useRealTimers documentation

💡 Result:

In Vitest, you use fake timers to gain control over time-dependent functions like setTimeout, setInterval, and Date, allowing you to run tests instantly instead of waiting for real time to pass [1][2]. Key functions and their usage: vi.useFakeTimers: Enables fake timers [3][2]. It is best practice to call this in a beforeEach hook to ensure a clean state for each test [1][4]. It intercepts calls to global timer APIs and, optionally, other platform methods like nextTick or queueMicrotask [2][5]. vi.useRealTimers: Restores the native timer implementations [3][2]. It is highly recommended to call this in an afterEach hook to avoid leaking mocked timer behavior into subsequent tests, which can cause instability or unexpected test failures [4]. Example Pattern: import { beforeEach, afterEach, describe, it, vi } from 'vitest'; describe('timer tests', => { beforeEach( => { // Enable fake timers before the test runs vi.useFakeTimers; }); afterEach( => { // Restore real timers after each test to prevent side effects vi.useRealTimers; }); it('example test', => { // Test logic here }); }); Important Considerations: - Date Mocking: vi.useFakeTimers also mocks the global Date object [3][2]. If you use vi.setSystemTime to control the date, remember that this state persists unless you manually reset it or restore real timers [6][7]. - Configuration: You can configure which APIs are faked (e.g., nextTick, requestAnimationFrame) by passing an options object to vi.useFakeTimers({ toFake: [...] }) [2][5]. - Cleanup: Always ensure cleanup occurs in afterEach, even if a test fails, to maintain a consistent environment for the rest of your test suite [4].

Citations:


Restore fake timers in afterEach.

This test enables fake timers, but vi.useRealTimers() only runs after the awaited rejection expectation. If the awaited promise rejects before that line runs, fake timers remain enabled and can affect later tests. Move vi.useRealTimers() into the existing afterEach block.

Proposed fix
 afterEach(() => {
   document.body.innerHTML = '';
+  vi.useRealTimers();
   vi.restoreAllMocks();
 });
...
-      vi.useRealTimers();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
afterEach(() => {
document.body.innerHTML = '';
vi.restoreAllMocks();
});
afterEach(() => {
document.body.innerHTML = '';
vi.useRealTimers();
vi.restoreAllMocks();
});
🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 20-20: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: document.body.innerHTML = ''
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation

(dom-content-modification)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.ts`
around lines 20 - 23, Update the existing afterEach cleanup in containerResolver
tests to call vi.useRealTimers() alongside vi.restoreAllMocks(), ensuring fake
timers are restored even when an awaited rejection exits early. Remove the later
success-path-only timer restoration if it becomes redundant.

Comment on lines +101 to +103
console.error(
'Cannot initialize Smart CAPTCHA widget because the `clerk-captcha` DOM element was not found; falling back to Invisible CAPTCHA widget. If you are using a custom flow, visit https://clerk.com/docs/guides/development/custom-flows/authentication/bot-sign-up-protection for instructions',
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the linked CAPTCHA documentation for Prosopo.

The fallback message directs developers to documentation that still states Cloudflare Turnstile is the CAPTCHA provider. Describe captcha_provider: "prosopo" and its configuration path before directing Prosopo users to this page.

As per coding guidelines, “Update documentation for API changes.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/clerk-js/src/utils/captcha/containerResolver.ts` around lines 101 -
103, Update the fallback message in the Smart CAPTCHA initialization path near
the relevant console.error call to describe the Prosopo configuration, including
captcha_provider: "prosopo" and its configuration path, before linking to the
custom-flow CAPTCHA documentation.

Sources: Coding guidelines, Linked repositories

@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants